package controler.algorithms.nnbp;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import controler.algorithms.Algorithm;
import controler.algorithms.NNBP_Extraction;

/** 
 * @file NNBP.java  
 * 
 **/

/**
 *    Implementacja interfejsu algorytmu wykorzystujaca siec neuronowa z propagacja wsteczna.
 *  
 *  Klasa implementujaca algorytm wykorzystujacy siec neuronowa z propagacja wsteczna.
 *  Klasa implementuje metody potrzebne do tworzenia, uczenia i testowania sieci.
 */

public class NNBP extends Algorithm {
	
	private NeuralNetwork network;
	
	private double learningFactor;
	private double momentum;
	private double acceptedError;
	private int maxEpochs;
	private int backSteps = 0;
	private String name;
	private long timeMax;
	private double lfCorrection = 0.5;
	
	
	/**
     *   Konstruktor tworzacy algorytm uzywajacy sieci neuronowej z propagacja wsteczna.
     * 
     * Konstruktor tworzy algorytm z siecia wedlug okreslonych kryteriow.
     * @param name nazwa algorytmu.
     * @param layersAndNeurons tablica definiujaca ilosc warstw i ilosc neuronow w kazdej warstwie.
     * @param learningFactor wspolczynnik uczenia.
     * @param momentum momentum.
     * @param acceptedError akceptowalny blad.
     * @param maxEpochs maksymalna ilosc epok.
     * @param bs ilosc poprzednich pomiarow branych pod uwage.
     * @param tm ilosc maksymalny czas uczenia.
     */
	public NNBP(String name, int[] layersAndNeurons, double learningFactor, double momentum, double acceptedError, int maxEpochs, int bs, long tm, double lfc)
	{
		this.name = name;
		this.learningFactor = learningFactor;
		this.momentum = momentum;
		this.acceptedError = acceptedError;
		this.maxEpochs = maxEpochs;
		this.backSteps = bs;
		this.timeMax = tm;
		this.lfCorrection = lfc;
		
		System.out.println("Stworzenie sieci");
		
		network = new NeuralNetwork(layersAndNeurons, learningFactor, momentum);
		super.setExtraction(new NNBP_Extraction(backSteps));
		
		super.setRecursionDepth(backSteps);
		
		super.setModelName(name);
		
		System.out.println("Siec stworzona");
	}
	
	
	/**
     *   Metoda uczaca siec.
     * 
     * Metoda uczaca siec neuronowa za pomoca podanego zbioru uczacego.
     * @param trainingSet lista bedaca zbiorem uczacym, kolejnymi tablicami odpowiednio przeskalowanych cech.
     * @return Zawsze true, jako ze uczenie nie moze sie nie udac.
     */
	public boolean learn(LinkedList<double[]> trainingSet)
	{
		System.out.println("Rozpoczecie uczenia: " + this.name);
		
		int epochCount = 0;
		Random r = new Random();
		
		double[] expectedOutput = new double[1];
		
		double epochError;
		double previousEpochError = 0;
		
		long timeStart = System.currentTimeMillis();
		long timeNow;
		
		while (epochCount < maxEpochs)
		{
			LinkedList<Integer> temporalTrainingSet = new LinkedList<Integer>();
			
			for (int i = 0 ; i < trainingSet.size() - 1 ; i++)
			{
				temporalTrainingSet.add(i);
			}
			
			epochError = 0;
			
			for (int i = 0 ; i < trainingSet.size() - 1 ; i++)
			{
				int current = r.nextInt(temporalTrainingSet.size());
				double[] currentFeatures = trainingSet.get(temporalTrainingSet.get(current));
				double[] nextFeatures = trainingSet.get(temporalTrainingSet.get(current) + 1);
				
				//double[] currentFeatures = trainingSet.get(i);
				//double[] nextFeatures = trainingSet.get(i + 1);
				
				double[] input = null;

				switch(backSteps)
				{
				case 1:
					input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16]};
					break;
				case 2:
					input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17]};
					break;
				case 3:
					input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18]};
					break;
				case 5:
					input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20]};
					break;
				case 10:
					input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25]};
					break;
				case 20:
					input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25], currentFeatures[26], currentFeatures[27], currentFeatures[28], currentFeatures[29], currentFeatures[30], currentFeatures[31], currentFeatures[32], currentFeatures[33], currentFeatures[34], currentFeatures[35]};
					break;
				case 50:
					input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25], currentFeatures[26], currentFeatures[27], currentFeatures[28], currentFeatures[29], currentFeatures[30], currentFeatures[31], currentFeatures[32], currentFeatures[33], currentFeatures[34], currentFeatures[35], currentFeatures[36], currentFeatures[37], currentFeatures[38], currentFeatures[39], currentFeatures[40], currentFeatures[41], currentFeatures[42], currentFeatures[43], currentFeatures[44], currentFeatures[45], currentFeatures[46], currentFeatures[47], currentFeatures[48], currentFeatures[49], currentFeatures[50], currentFeatures[51], currentFeatures[52], currentFeatures[53], currentFeatures[54], currentFeatures[55], currentFeatures[56], currentFeatures[57], currentFeatures[58], currentFeatures[59], currentFeatures[60], currentFeatures[61], currentFeatures[62], currentFeatures[63], currentFeatures[64], currentFeatures[65]};
					break;
				}
				
				expectedOutput[0] = 0.5 + ((1/130.0) * (nextFeatures[0] - currentFeatures[0]));
				
				temporalTrainingSet.remove(current);
				
				network.propagation(input, expectedOutput);
				
				epochError += network.mse(expectedOutput);
			}
			
			epochCount++;
			
			System.out.println("Epok: " + epochCount);
			System.out.println("Blad: " + epochError);
			
			timeNow = System.currentTimeMillis();
			
			if(epochError < this.acceptedError)
				break;
			
			if(timeNow - timeStart > timeMax)
				break;
			
			if((previousEpochError - epochError) < 0 && epochCount > 1)
			{
				double lf = network.getLearningRate();
				network.setLearningRate(lf * lfCorrection);
				System.out.println("Zmniejszono wspolczynnik uczenia: " + network.getLearningRate());
			}
			
			previousEpochError = epochError;
		}
		return true;
	}
	
	
	/**
     *   Metoda testujaca siec.
     * 
     * Metoda testujaca siec neuronowa za pomoca podanego zbioru testowego i wypisujaca efekt do konsoli.
     * @param validationSet lista bedaca zbiorem testowym, kolejnymi tablicami odpowiednio przeskalowanych cech.
     */
	public void test(LinkedList<double[]> validationSet)
	{
		double[] expectedOutput = new double[1];
		
		for (int i = 0 ; i < validationSet.size() - 1 ; i++)
		{
			double[] currentFeatures = validationSet.get(i);
			double[] nextFeatures = validationSet.get(i + 1);
			double[] input = null;
			
			switch(backSteps)
			{
			case 1:
				input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16]};
				break;
			case 2:
				input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17]};
				break;
			case 3:
				input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18]};
				break;
			case 5:
				input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20]};
				break;
			case 10:
				input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25]};
				break;
			case 20:
				input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25], currentFeatures[26], currentFeatures[27], currentFeatures[28], currentFeatures[29], currentFeatures[30], currentFeatures[31], currentFeatures[32], currentFeatures[33], currentFeatures[34], currentFeatures[35]};
				break;
			case 50:
				input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25], currentFeatures[26], currentFeatures[27], currentFeatures[28], currentFeatures[29], currentFeatures[30], currentFeatures[31], currentFeatures[32], currentFeatures[33], currentFeatures[34], currentFeatures[35], currentFeatures[36], currentFeatures[37], currentFeatures[38], currentFeatures[39], currentFeatures[40], currentFeatures[41], currentFeatures[42], currentFeatures[43], currentFeatures[44], currentFeatures[45], currentFeatures[46], currentFeatures[47], currentFeatures[48], currentFeatures[49], currentFeatures[50], currentFeatures[51], currentFeatures[52], currentFeatures[53], currentFeatures[54], currentFeatures[55], currentFeatures[56], currentFeatures[57], currentFeatures[58], currentFeatures[59], currentFeatures[60], currentFeatures[61], currentFeatures[62], currentFeatures[63], currentFeatures[64], currentFeatures[65]};
				break;
			}
			
			network.execution(input);
			
			double[] output = network.getOutputs();
			
			double out = output[0];
			
			out = 130 * (out - 0.5);
			
			System.out.println("Wartosc otrzymana: " + currentFeatures[0] + out);
			System.out.println("Powinna byc: " + nextFeatures[0]);
		}
	}

	/**
     *   Metoda wykorzystujaca nauczona siec do przewidzenia ruchu za 10 minut.
     * 
     * Metoda wykorzystujaca nauczona siec do przewidzenia ruchu za 10 minut wzgledam najnowszego z podanych pomiarow.
     * @param dataSet lista bedaca zbiorem pomiarow, kolejnymi tablicami odpowiednio przeskalowanych cech,
     * sa to kolejne pomiary poprzedzajace moment przewidywania, jest ich tyle ile zostalo zdefiniowane w konstruktorze algorytmu.
     * @return Przewidywane natezenie za 10 minut.
     */
	@Override
	public double predict(LinkedList<double[]> dataSet) {
		
		double[] currentFeatures = dataSet.get(dataSet.size() - 1);
		
		double[] input = null;
		
		switch(backSteps)
		{
		case 1:
			input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16]};
			break;
		case 2:
			input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17]};
			break;
		case 3:
			input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18]};
			break;
		case 5:
			input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20]};
			break;
		case 10:
			input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25]};
			break;
		case 20:
			input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25], currentFeatures[26], currentFeatures[27], currentFeatures[28], currentFeatures[29], currentFeatures[30], currentFeatures[31], currentFeatures[32], currentFeatures[33], currentFeatures[34], currentFeatures[35]};
			break;
		case 50:
			input = new double[]{currentFeatures[1], currentFeatures[2], currentFeatures[3], currentFeatures[4], currentFeatures[5], currentFeatures[6], currentFeatures[7], currentFeatures[8], currentFeatures[9], currentFeatures[15], currentFeatures[16], currentFeatures[17], currentFeatures[18], currentFeatures[19], currentFeatures[20], currentFeatures[21], currentFeatures[22], currentFeatures[23], currentFeatures[24], currentFeatures[25], currentFeatures[26], currentFeatures[27], currentFeatures[28], currentFeatures[29], currentFeatures[30], currentFeatures[31], currentFeatures[32], currentFeatures[33], currentFeatures[34], currentFeatures[35], currentFeatures[36], currentFeatures[37], currentFeatures[38], currentFeatures[39], currentFeatures[40], currentFeatures[41], currentFeatures[42], currentFeatures[43], currentFeatures[44], currentFeatures[45], currentFeatures[46], currentFeatures[47], currentFeatures[48], currentFeatures[49], currentFeatures[50], currentFeatures[51], currentFeatures[52], currentFeatures[53], currentFeatures[54], currentFeatures[55], currentFeatures[56], currentFeatures[57], currentFeatures[58], currentFeatures[59], currentFeatures[60], currentFeatures[61], currentFeatures[62], currentFeatures[63], currentFeatures[64], currentFeatures[65]};
			break;
		}
		
		network.execution(input);
		
		double[] output = network.getOutputs();
		double out = output[0];
		
		out = 130 * (out - 0.5);
		
		return currentFeatures[0] + out;
	}

	/**
     *   Metoda zwracajaca nazwe algorytmu.
     * @return Nazwa algorytmu.
     */
	@Override
	public String getModelName() {
		return name;
	}

}
